package com.google.code.virtualhockey.vhx.server.servlet.rpc

import scala.collection.mutable.HashMap
import com.google.code.virtualhockey.vhx.shared.rpc.{IResponse, ICommand}
import com.google.code.virtualhockey.vhx.shared.error.WebNoCommandHandlerException

/**
 * Utility to look up command handlers
 */
trait TCommandHandlerLookup {
  private val _mapCmdCache: HashMap[String,TCommandHandler[_,_]] = HashMap.empty

  /**
   * Finds and returns the handler that matches the given command.
   */
  final def getHandler[A <: ICommand[IResponse]]( cmd: A ): TCommandHandler[A,IResponse] = {
    val strKey = getCacheKey( cmd )
    var optHandler = getFromCache( strKey ).asInstanceOf[Option[TCommandHandler[A,IResponse]]]
    if ( optHandler == None ) {
      optHandler = lookupHandler( cmd ).asInstanceOf[Option[TCommandHandler[A,IResponse]]]
      if ( optHandler == None )
        throw new WebNoCommandHandlerException( cmd )
      else 
        _mapCmdCache += ( strKey -> optHandler.get )
    }
    optHandler.get
  }

  /**
   * <p>
   * Returns the key that should be used to cache already looked up handlers. By
   * default the key is composed of the ICommand implementation class name. If
   * an additional info ({@link ICommand#getDispatchInfo()} is provided by the command,
   * that info is appended to the class name. The class name and the info are
   * separated by '$$$'.
   * </p>
   *
   * <p>
   * Please override if you need more control over command handler caching.
   * </p>
   *
   * @param cmd the command for which to obtain a cache key
   * @return the key used for command caching
   */
  private def getCacheKey[A <: ICommand[_]]( cmd: A ): String = {
    val sb = new StringBuilder( cmd.getClass.getName )
    if ( cmd.getDispatchInfo != null )
      sb.append("$$$").append( cmd.getDispatchInfo )
    sb.toString
  }

  /**
   * Tries to load the command handler from the command handler cache using the
   * given key.
   *
   * @param strKey the cache key
   * @return the found command handler or <code>None</code>
   */
  private def getFromCache( strKey: String ): Option[TCommandHandler[_,_]] = {
    _mapCmdCache.get( strKey )
  }

  /**
   * Looks up the {@link TCommandHandler} that matches the given command. Please
   * do not use any caching mechanism in the method's implementation as this
   * method is only called if no command handler had already been found in the
   * handler cache.
   *
   * @param cmd the command for which to get a handler
   * @return the found handler or <code>None</code> to indicate that no handler
   *         was found
   */
  protected def lookupHandler( cmd: ICommand[_] ): Option[TCommandHandler[_,_]]
}
